# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: jaewon

import datetime
import functools

from coin.base.param_util import to_list
from coin.base.datetime_util import to_datetime
from coin.exchange.kr_rest.product_holders import get_holder_from_product
from coin.exchange.okex_futures.kr_rest.futures_product import (OkexFuturesProduct)
from coin.exchange.okex.kr_rest.product import OkexProduct
from coin.exchange.huobi.kr_rest.product import HuobiProduct
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct
from coin.exchange.kraken.kr_rest.product import KrakenProduct
from coin.exchange.gdax.kr_rest.product import GdaxProduct
from coin.exchange.binance.kr_rest.product import BinanceProduct

from coin.strategy.mm.simple_sim import result_util
from coin.strategy.mm.simple_sim.strategy.pass_unhedge_3 import (PassUnhedgedSimStrategy)
from coin.strategy.mm.simple_sim.profile.pass_unhedge_xbtusd_3 import (linear_sell_edge,
                                                                       linear_buy_edge)
from coin.exchange.kraken.kr_rest.constants import min_order_qty

current_datetime = datetime.datetime(2018, 7, 29)

config_okex_futures = [
    {
        'ref_product': OkexFuturesProduct.FromStr('BTC-USD.QUARTER', current_datetime),
        'trade_product': KrakenProduct.FromStr('BTC-USD'),
    },
    {
        'ref_product': OkexFuturesProduct.FromStr('ETH-USD.QUARTER', current_datetime),
        'trade_product': KrakenProduct.FromStr('ETH-USD'),
    },
    {
        'ref_product': OkexFuturesProduct.FromStr('BCH-USD.QUARTER', current_datetime),
        'trade_product': KrakenProduct.FromStr('BCH-USD'),
    },
    {
        'ref_product': OkexFuturesProduct.FromStr('EOS-USD.QUARTER', current_datetime),
        'trade_product': KrakenProduct.FromStr('EOS-USD'),
    },
    {
        'ref_product': OkexFuturesProduct.FromStr('ETC-USD.QUARTER', current_datetime),
        'trade_product': KrakenProduct.FromStr('ETC-USD'),
    },
    {
        'ref_product': OkexFuturesProduct.FromStr('XRP-USD.QUARTER', current_datetime),
        'trade_product': KrakenProduct.FromStr('XRP-USD'),
    },
]

config_okex = [
    # Okex
    {
        'ref_product': OkexProduct.FromStr('BTC-USDT'),
        'trade_product': KrakenProduct.FromStr('BTC-USD'),
    },
    {
        'ref_product': OkexProduct.FromStr('ETH-USDT'),
        'trade_product': KrakenProduct.FromStr('ETH-USD'),
    },
    {
        'ref_product': OkexProduct.FromStr('BCH-USDT'),
        'trade_product': KrakenProduct.FromStr('BCH-USD'),
    },
    {
        'ref_product': OkexProduct.FromStr('EOS-USDT'),
        'trade_product': KrakenProduct.FromStr('EOS-USD'),
    },
    {
        'ref_product': OkexProduct.FromStr('ETC-USDT'),
        'trade_product': KrakenProduct.FromStr('ETC-USD'),
    },
    {
        'ref_product': OkexProduct.FromStr('XRP-USDT'),
        'trade_product': KrakenProduct.FromStr('XRP-USD'),
    },
]

config_huobi = [
    # Huobi
    {
        'ref_product': HuobiProduct.FromStr('BTC-USDT'),
        'trade_product': KrakenProduct.FromStr('BTC-USD'),
    },
    {
        'ref_product': HuobiProduct.FromStr('ETH-USDT'),
        'trade_product': KrakenProduct.FromStr('ETH-USD'),
    },
    {
        'ref_product': HuobiProduct.FromStr('BCH-USDT'),
        'trade_product': KrakenProduct.FromStr('BCH-USD'),
    },
    {
        'ref_product': HuobiProduct.FromStr('EOS-USDT'),
        'trade_product': KrakenProduct.FromStr('EOS-USD'),
    },
    {
        'ref_product': HuobiProduct.FromStr('ETC-USDT'),
        'trade_product': KrakenProduct.FromStr('ETC-USD'),
    },
    {
        'ref_product': HuobiProduct.FromStr('XRP-USDT'),
        'trade_product': KrakenProduct.FromStr('XRP-USD'),
    },
]

config_bitfinex_usd = [
    # Bitfinex USD
    {
        'ref_product': BitfinexProduct.FromStr('BTC-USD'),
        'trade_product': KrakenProduct.FromStr('BTC-USD'),
    },
    {
        'ref_product': BitfinexProduct.FromStr('ETH-USD'),
        'trade_product': KrakenProduct.FromStr('ETH-USD'),
    },
    {
        'ref_product': BitfinexProduct.FromStr('BCH-USD'),
        'trade_product': KrakenProduct.FromStr('BCH-USD'),
    },
    {
        'ref_product': BitfinexProduct.FromStr('EOS-USD'),
        'trade_product': KrakenProduct.FromStr('EOS-USD'),
    },
    {
        'ref_product': BitfinexProduct.FromStr('ETC-USD'),
        'trade_product': KrakenProduct.FromStr('ETC-USD'),
    },
    {
        'ref_product': BitfinexProduct.FromStr('XRP-USD'),
        'trade_product': KrakenProduct.FromStr('XRP-USD'),
    },
]

config_bitfinex_eur = [
    # Bitfinex EUR
    {
        'ref_product': BitfinexProduct.FromStr('BTC-EUR'),
        'trade_product': KrakenProduct.FromStr('BTC-EUR'),
    },
    {
        'ref_product': BitfinexProduct.FromStr('ETH-EUR'),
        'trade_product': KrakenProduct.FromStr('ETH-EUR'),
    },
    {
        'ref_product': BitfinexProduct.FromStr('EOS-EUR'),
        'trade_product': KrakenProduct.FromStr('EOS-EUR'),
    },
]

config_gdax_usd = [
    {
        'ref_product': GdaxProduct.FromStr('BTC-USD'),
        'trade_product': KrakenProduct.FromStr('BTC-USD'),
    },
    {
        'ref_product': GdaxProduct.FromStr('ETH-USD'),
        'trade_product': KrakenProduct.FromStr('ETH-USD'),
    },
]

config_gdax_eur = [
    {
        'ref_product': GdaxProduct.FromStr('BTC-EUR'),
        'trade_product': KrakenProduct.FromStr('BTC-EUR'),
    },
    {
        'ref_product': GdaxProduct.FromStr('ETH-EUR'),
        'trade_product': KrakenProduct.FromStr('ETH-EUR'),
    },
]

config_binance = [
    {
        'ref_product': BinanceProduct.FromStr('BTC-USDT'),
        'trade_product': KrakenProduct.FromStr('BTC-USD'),
    },
    {
        'ref_product': BinanceProduct.FromStr('ETH-USDT'),
        'trade_product': KrakenProduct.FromStr('ETH-USD'),
    },
    {
        'ref_product': BinanceProduct.FromStr('ETC-USDT'),
        'trade_product': KrakenProduct.FromStr('ETC-USD'),
    },
    {
        'ref_product': BinanceProduct.FromStr('EOS-USDT'),
        'trade_product': KrakenProduct.FromStr('EOS-USD'),
    },
    {
        'ref_product': BinanceProduct.FromStr('BCC-USDT'),
        'trade_product': KrakenProduct.FromStr('BCH-USD'),
    },
]

config_okex_btc = [
    {
        'ref_product': OkexProduct.FromStr('ETH-BTC'),
        'trade_product': KrakenProduct.FromStr('ETH-BTC'),
    },
    {
        'ref_product': OkexProduct.FromStr('BCH-BTC'),
        'trade_product': KrakenProduct.FromStr('BCH-BTC'),
    },
    {
        'ref_product': OkexProduct.FromStr('EOS-BTC'),
        'trade_product': KrakenProduct.FromStr('EOS-BTC'),
    },
]

config = []


def prepare(profile_args, timestamp):
  global config
  global current_datetime

  current_datetime = datetime.datetime.strptime(profile_args[1], '%Y%m%d')
  config = globals()[profile_args[0]]
  for single_config in config:
    single_config['lot_size'] = 10 * min_order_qty[single_config['trade_product'].base.currency]
    single_config['min_pos'] = single_config['lot_size'] * 2
    single_config['max_pos'] = single_config['lot_size'] * 10
    single_config['ma_window'] = [1, 5, 10]
    single_config['edge_bp'] = [0.5, 1, 1.5, 2]
    single_config['price_det_notional'] = 800


def get_products():
  ref_products = [single_config['ref_product'] for single_config in config]
  trade_products = [single_config['trade_product'] for single_config in config]
  return ref_products + trade_products


def get_machines():
  # return ['feed-01.us-east-1.aws']
  return ['feed-01.eu-west-1.aws', 'feed-02.us-east-1.aws']


def get_time_ranges():
  ranges = []
  cur_dt = current_datetime
  end_dt = current_datetime + datetime.timedelta(days=1)
  while cur_dt < end_dt:
    ranges.append((cur_dt, cur_dt + datetime.timedelta(hours=24)))
    cur_dt += datetime.timedelta(hours=24)
  return ranges


def get_lot_size(timestamp):
  dt = to_datetime(timestamp)
  if 0 <= dt.hour <= 15:
    return 0.05
  else:
    return 0.05


def get_min_pos(timestamp):
  return 0.1


def get_max_pos(timestamp):
  return 0.5


def get_strategy(from_ts, to_ts):
  strategy_list = []
  for single_config in config:
    pass_product = single_config['trade_product']
    ref_product = single_config['ref_product']
    notional = single_config['price_det_notional']
    pass_product_true_book_funcs = (
        (lambda book: book.get_notional_asks_by_amt(notional)[1][-1][0]),
        (lambda book: book.get_notional_bids_by_amt(notional)[1][-1][0]))

    ref_product_true_book_funcs = ((lambda book: book.get_notional_asks_by_amt(notional)[1][-1][0]),
                                   (lambda book: book.get_notional_bids_by_amt(notional)[1][-1][0]))

    NS_PER_SECOND = (10**9)
    NS_PER_MINUTE = 60 * NS_PER_SECOND

    for basis_ma_window in single_config['ma_window']:
      for edge_bp in single_config['edge_bp']:
        for agg_edge in [None]:
          for close_edge_bp in [edge_bp]:
            min_pos = single_config['min_pos']
            max_pos = single_config['max_pos']
            lot_size = single_config['lot_size']
            tick = get_holder_from_product(pass_product).product_info.gen_tick()

            pricing_param = {
                'basis_ma_window': basis_ma_window * NS_PER_MINUTE,
                'tick': tick,
                'book_askt_func_1': ref_product_true_book_funcs[0],
                'book_bidt_func_1': ref_product_true_book_funcs[1],
                'book_askt_func_2': pass_product_true_book_funcs[0],
                'book_bidt_func_2': pass_product_true_book_funcs[1]
            }

            executor_param = {
                'lot_size': lot_size,
                'min_pos': min_pos,
                'max_pos': max_pos,
                'maker_fee': 0. / 10000.,
                'taker_fee': 0. / 10000.,
                'execution_delay': 1 * NS_PER_SECOND,
                'post_only': False,
                'trade_qty_func': (lambda trade: 100 * trade.qty / trade.price)
            }

            use_agg = 'without_agg'
            if agg_edge:
              use_agg = 'with_agg'
            name = '%s.%s.%05ddet.%02dm.%02dbp.%02dbp.%s.%s' % (
                pass_product.exchange,
                pass_product.symbol,
                notional,
                basis_ma_window,
                edge_bp,
                close_edge_bp,
                use_agg,
                to_datetime(from_ts).strftime('%Y%m%d'))

            strategy = PassUnhedgedSimStrategy(pass_product,
                                               ref_product,
                                               functools.partial(linear_sell_edge,
                                                                 edge_bp / 10000.,
                                                                 close_edge_bp / 10000.,
                                                                 1.),
                                               functools.partial(linear_buy_edge,
                                                                 edge_bp / 10000.,
                                                                 close_edge_bp / 10000.,
                                                                 1.),
                                               pricing_param,
                                               executor_param,
                                               trade_after=basis_ma_window,
                                               name=name,
                                               agg_edge=agg_edge,
                                               feature_filepath=('out/feature.%s.csv' % name),
                                               fill_filepath=('out/fill.%s.csv' % name))
            strategy_list.append(strategy)

  return strategy_list


def get_strategy_result(strategy):
  return {'name': strategy.name, **strategy.get_summary()}


def aggregate_result(results):
  return result_util.aggregate_sim_result(results)
